%%%-------------------------------------------------------------------
%%% @author Anil Kumar S. R. <sranil@gmail.com>
%%% @copyright (C) 2010, 
%%% @doc
%%%
%%% @end
%%% Created : 15 Sep 2010 by Anil Kumar S. R. <sranil@gmail.com>
%%%-------------------------------------------------------------------
-module(sip_session).

-behaviour(gen_server).

%% API
-export([start_link/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
		 terminate/2, code_change/3]).

-include("../include/b2bua.hrl").
-include("../include/sip.hrl").
-include("../include/stun_header.hrl").

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link([B2BUA_Session_PID]) ->
	gen_server:start_link(?MODULE, [B2BUA_Session_PID], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
init([B2BUA_Session_PID]) ->
    case catch sip_transaction:start_link([self()]) of 
        {ok, PID} ->
            Dialog_ID = ets:new(dialog_table, [set, {keypos, #dialog.call_id}]),
            Transaction_Table_ID = ets:new(transaction_table, [set, {keypos, #transaction.branch_id}]),
            {ok, #sip_session_state{b2bua_session_pid = B2BUA_Session_PID, ua_state = ?NOT_REGISTERED, cseq = 1, dialog_table = Dialog_ID, transaction_table = Transaction_Table_ID, ti_pid = PID}};
        _ ->
            {stop, "Unable to start sip_transaction process"}
    end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @spec handle_call(Request, From, State) ->
%%                                   {reply, Reply, State} |
%%                                   {reply, Reply, State, Timeout} |
%%                                   {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, Reply, State} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_call({register, #register{userid = [Userid], password = [Password]}}, _, State) ->
    case create_socket() of 
        {ok, Socket} ->
            New_State = stun_binding_request(Socket, State),
            Return_Response = return_response("register", "success", "101", "Registration in Progress"),
            Response = ["<talk_response>", Return_Response, "</talk_response>"],
            {reply, {ok, Response}, New_State#sip_session_state{socket = Socket, userid = Userid, password = Password}};
        error ->
            Return_Response = return_response("register", "critical", "504", "Request Failed"),
			Response = ["<talk_response>", Return_Response, "</talk_response>"],
            {reply, {error, Response}, State}
    end;    

handle_call(_Request, _From, State) ->
	Reply = ok,
	{reply, Reply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @spec handle_cast(Msg, State) -> {noreply, State} |
%%                                  {noreply, State, Timeout} |
%%                                  {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_cast({refresh_nat_binding, Socket}, State) ->
    stun:binding_request(?STUN_SERVER, ?STUN_PORT, Socket),
    case timer:apply_after(?REFRESH_TIMER*1000, gen_server, cast, [self(), {refresh_nat_binding, Socket}]) of
        {ok, TRef} ->
            {noreply, State#sip_session_state{stun_timer = TRef}};
        _ ->
            {noreply, State#sip_session_state{stun_timer = -1}}
    end;

handle_cast(register, State) ->
    gen_server:cast(self(), {register, ?EXPIRES, State}),
    {noreply, State};

handle_cast({register, Expires, State}, _) ->
    io:format("Inside register function ~n"),
    if
        Expires == 0 ->
            case catch timer:apply_after(?T1 * 64, gen_server, cast, [self(), kill_process]) of
                {ok, TRef} ->
                    nothing;
                _ ->
                    TRef = -1
            end;
        true ->
            TRef = -1
    end,
    #sip_session_state{ti_pid = TI_PID} = State,
	{Branch_ID, Request, New_State} = uac_core:packet([register, noauth, Expires, State]),
    gen_server:cast(TI_PID, {create_ti, {udp, ?SIP_SERVER, ?SIP_PORT}, Branch_ID, "REGISTER", Request}),
    {noreply, New_State#sip_session_state{deregister_timer = TRef}};

handle_cast(deregister, State) ->
    gen_server:cast(self(), {register, 0, State}),
    {noreply, State};

handle_cast({invite, #invite{to = [To]}}, State) when State#sip_session_state.ua_state == ?REGISTERED ->
    io:format("Inside invite function ~n"),
    #sip_session_state{ti_pid = TI_PID} = State,
    case catch uac_core:packet([invite, noauth, To, self(), State]) of 
        {error, Code} ->
            Return_Response = return_response("invite", "error", Code, "Call Failed"),
            gen_server:cast(self(), {response, normal, Return_Response}),
            {noreply, State};
        {Branch_ID, Request, New_State} -> 
            gen_server:cast(TI_PID, {create_ti, {udp, ?SIP_SERVER, ?SIP_PORT}, Branch_ID, "INVITE", Request}),
            {noreply, New_State}
    end;

handle_cast({invite, _}, State) ->
    #sip_session_state{ua_state = UA_State} = State,
    case UA_State of
        ?NOT_REGISTERED ->
            Return_Response = return_response("invite", "critical", "105", "User not logged in"),
            gen_server:cast(self(), {response, normal, Return_Response}),
            {noreply, State};
        ?CALL_IN_PROGRESS ->
            Return_Response = return_response("invite", "error", "106", "Call failed"),
            gen_server:cast(self(), {response, normal, Return_Response}),
            {noreply, State}
    end;

handle_cast({cancel, #cancel{to = [To]}}, State) when State#sip_session_state.ua_state == ?CALL_IN_PROGRESS ->
    io:format("Inside cancel function ~n"),
    #sip_session_state{ti_pid = TI_PID, dialog_table = Dialog_Table, transaction_table = Transaction_Table} = State,

    Transaction_List = ets:match_object(Transaction_Table, #transaction{method = "INVITE", _='_'}),
    if
        length(Transaction_List) >= 1 ->
            %% Generate cancel request
            Transaction_State = hd(Transaction_List),
            catch ets:match_delete(Transaction_Table, #transaction{method = "INVITE", _='_'}),
            {Branch_ID, Request} = uac_core:packet([cancel, To, Transaction_State, State]),
            gen_server:cast(TI_PID, {create_ti, {udp, ?SIP_SERVER, ?SIP_PORT}, Branch_ID, "CANCEL", Request}),
            {noreply, State};
        true ->
            Dialog_List = ets:match_object(Dialog_Table, #dialog{method = 'INVITE', _='_'}),
            if
                length(Dialog_List) >= 1 ->
                    %% Generate bye request
                    {Branch_ID, Request, New_State} = uac_core:packet([bye, hd(Dialog_List), State]),
                    gen_server:cast(TI_PID, {create_ti, {udp, ?SIP_SERVER, ?SIP_PORT}, Branch_ID, "BYE", Request}),
                    {noreply, New_State};
                true ->
                    Return_Response = return_response("cancel", "error", "109", "No call in progress to cancel"),
                    gen_server:cast(self(), {response, normal, Return_Response}),
                    {noreply, State#sip_session_state{ua_state = ?REGISTERED}}
            end
    end;

handle_cast({cancel, _}, State) ->
    Return_Response = return_response("cancel", "error", "109", "No call in progress to cancel"),
    gen_server:cast(self(), {response, normal, Return_Response}),
    {noreply, State};

handle_cast({accept, #accept{from = [_From]}}, State) ->
    #sip_session_state{ti_pid = TI_PID, socket = Socket, transaction_table = Transaction_Table, dialog_table = Dialog_Table, confirm_timer = Confirm_Timer, parse_record = Parse_Record} = State,
    #parse_record{branch_id = Branch_ID, route = Route, from = From, from_tag = From_Tag, to = To, to_tag = To_Tag, call_id = Call_ID, cseq = CSeq, ip = IP, port = Port, contact = Contact_List} = Parse_Record,
    
    if
        Confirm_Timer /= -1 ->
            timer:cancel(Confirm_Timer);
        true ->
            nothing
    end,

    if
        Parse_Record /= undefined ->
            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
    
            case uas_core:packet([before_dialog, "200", "OK", Parse_Record, self(), State]) of
                {Branch_ID, Accept_Response, New_State} ->
                    #contact_record{contact = Contact} = hd(Contact_List),
                    Dialog = #dialog{call_id = Call_ID, method = "INVITE", route = Route, remote_target = Contact, local_cseq = "", remote_cseq = CSeq, local_tag = To_Tag, remote_tag = From_Tag, local_uri = To, remote_uri = From},
                    catch ets:match_delete(Dialog_Table, #dialog{method = "INVITE", _='_'}),
                    catch ets:insert(Dialog_Table, Dialog),

                    send_data(Socket, udp, IP, Port, Accept_Response);
                _ ->
                    Return_Response = return_response("invite", "error", "106", "Call Failed"),
                    gen_server:cast(self(), {response, normal, Return_Response}),
                    
                    {Decline_Response, Parse_Record_1} = uas_core:packet([general, "603", "Decline", Parse_Record, State]),
                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_1#parse_record.branch_id, "603", Decline_Response}),
                    
                    New_State = State#sip_session_state{ua_state = ?REGISTERED}
            end;
        true ->
            New_State = State#sip_session_state{ua_state = ?REGISTERED}
    end,
    {noreply, New_State#sip_session_state{confirm_timer = -1, parse_record = undefined}};

handle_cast({reject, #reject{from = [From_Name]}}, State) ->
    io:format("Inside reject ~n"),
    #sip_session_state{ti_pid = TI_PID, confirm_timer = Confirm_Timer, parse_record = Parse_Record, b2bua_media_listener_pid = B2BUA_Media_PID} = State,
    if
        Confirm_Timer /= -1 ->
            timer:cancel(Confirm_Timer);
        true ->
            nothing
    end,
    remove_b2bua_media_session(B2BUA_Media_PID),
    if
        Parse_Record /= undefined ->
            #parse_record{ip = IP, port = Port} = Parse_Record,
            From = utils:replace_predefined_xml_entities_to_character(From_Name),
            
            if
                Parse_Record#parse_record.from == From ->
                    {Decline_Response, Parse_Record_1} = uas_core:packet([general, "603", "Decline", Parse_Record, State]),
                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_1#parse_record.branch_id, "603", Decline_Response});
                true ->
                    nothing
            end;
        true ->
            nothing
    end,
    {noreply, State#sip_session_state{ua_state = ?REGISTERED, confirm_timer = -1, parse_record = undefined, b2bua_media_listener_pid = -1}};

handle_cast({reject, timeout, R}, State) ->
    Return_Response = return_response("", "error", "108", "Call cancelled"),
    gen_server:cast(self(), {response, normal, Return_Response}),
    gen_server:cast(self(), {reject, R}),
    {noreply, State};

handle_cast({bye, #bye{to = [_To]}}, State) ->
    io:format("Inside bye message ~n"),
    #sip_session_state{ti_pid = TI_PID, dialog_table = Dialog_Table} = State,
    
    Dialog_List = ets:match_object(Dialog_Table, #dialog{method = "INVITE", _='_'}),
    
    if
        Dialog_List /= [] ->
            Dialog_State = hd(Dialog_List),
            {Branch_ID, Request, New_State} = uac_core:packet([bye, Dialog_State, State]),
            gen_server:cast(TI_PID, {create_ti, {udp, ?SIP_SERVER, ?SIP_PORT}, Branch_ID, "BYE", Request});
        true ->
            New_State = State
    end,
    {noreply, New_State};

handle_cast({timeout, Branch_ID, Method}, State) ->
    io:format("Inside timeout function ~n"),
    #sip_session_state{transaction_table = Transaction_Table} = State,
    if
        Method == "REGISTER" ->
            Return_Response = return_response("register", "critical", "104", "Registration Timeout"),
            gen_server:cast(self(), {response, exit, Return_Response}),
            New_State = State;
        Method == "INVITE" ->
            Return_Response = return_response("invite", "error", "106", "Call Failed"),
            gen_server:cast(self(), {response, normal, Return_Response}),
            New_State = State#sip_session_state{ua_state = ?REGISTERED};
        Method == "CANCEL" ->
            New_State = State#sip_session_state{ua_state = ?REGISTERED};
        true ->
            New_State = State
    end,
    catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
    {noreply, New_State};
    
handle_cast({got_response, Parse_Record}, State) ->
    #parse_record{status = Status} = Parse_Record,
    Status_Class = Status div 100,
    New_State = 
        case Status_Class of 
            1 ->
                got_provisional_response(Parse_Record, State);
            2 ->
                got_success_response(Parse_Record, State);
            3 ->
                got_redirect_response(Parse_Record, State);
            4 ->
                got_client_error_response(Parse_Record, State);
            5 ->
                got_server_error_response(Parse_Record, State);
            6 ->
                got_global_error_response(Parse_Record, State)
        end,
    {noreply, New_State};

handle_cast({got_request, Parse_Record}, State) ->
    #parse_record{method = Method} = Parse_Record,
    case Method of
        "INVITE" ->
            #sip_session_state{socket = Socket, ua_state = UA_State, ti_pid = TI_PID} = State,
            #parse_record{ip = IP, port = Port, from = From} = Parse_Record,
            case UA_State of
                ?CALL_IN_PROGRESS ->
                    {Decline_Response, Parse_Record_1} = uas_core:packet([general, "603", "Decline", Parse_Record, State]),
                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_1#parse_record.branch_id, "603", Decline_Response}),
                    New_State = State;
                _ ->
                    {Trying_Response, Parse_Record_1} = uas_core:packet([general, "101", "Trying", Parse_Record, State]),
                    send_data(Socket, udp, IP, Port, Trying_Response),

                    if
                        (Parse_Record_1#parse_record.sdp)#sdp.media == [] ->
                            {Unacceptable_Response, Parse_Record_2} = uas_core:packet([general, "488", Parse_Record_1, State]),
                            gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_2#parse_record.branch_id, "488", Unacceptable_Response}),
                            New_State = State;
                        true ->
                            case catch b2bua_media_listener:start() of 
                                {ok, B2BUA_Media_PID} ->
                                    case catch gen_server:call(B2BUA_Media_PID, create_socket, infinity) of 
                                        {ok, B2BUA_Media_Port} ->
                                            {Ringing_Response, Parse_Record_2} = uas_core:packet([general, "180", "Ringing", Parse_Record_1, State]),
                                            send_data(Socket, udp, IP, Port, Ringing_Response),
                                            Call_Request = call_request(utils:replace_character_to_predefined_xml_entities(From), (Parse_Record_2#parse_record.sdp)#sdp.media, B2BUA_Media_Port),
                                            gen_server:cast(self(), {response, normal, Call_Request}),
                                            case catch timer:apply_after(45 * 1000, gen_server, cast, [self(), {reject, timeout, #reject{from = [From]}}]) of
                                                {ok, TRef} ->
                                                    New_State = State#sip_session_state{ua_state = ?CALL_IN_PROGRESS, confirm_timer = TRef, parse_record = Parse_Record_2, b2bua_media_listener_pid = B2BUA_Media_PID};
                                                _ ->
                                                    New_State = State#sip_session_state{ua_state = ?CALL_IN_PROGRESS, parse_record = Parse_Record_2, b2bua_media_listener_pid = B2BUA_Media_PID}
                                            end;
                                        _ ->
                                            {Unacceptable_Response, Parse_Record_2} = uas_core:packet([general, "488", Parse_Record_1, State]),
                                            gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_2#parse_record.branch_id, "488", Unacceptable_Response}),
                                            remove_b2bua_media_session(B2BUA_Media_PID),
                                            New_State = State
                                    end;
                                _ ->
                                    {Unacceptable_Response, Parse_Record_2} = uas_core:packet([general, "488", Parse_Record_1, State]),
                                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Parse_Record_2#parse_record.branch_id, "488", Unacceptable_Response}),
                                    New_State = State
                            end
                    end
            end;
        "CANCEL" ->
            #sip_session_state{socket = Socket, confirm_timer = Confirm_Timer, ti_pid = TI_PID, parse_record = Old_Parse_Record, b2bua_media_listener_pid = B2BUA_Media_PID} = State,
            #parse_record{ip = IP, port = Port, from = From} = Parse_Record,

            Cancel_Request = cancel_request(utils:replace_character_to_predefined_xml_entities(From)),
            gen_server:cast(self(), {response, normal, Cancel_Request}),

            {Cancel_Response, _} = uas_core:packet([general, "200", "OK", Parse_Record, State]),
            send_data(Socket, udp, IP, Port, Cancel_Response),

            if
                Old_Parse_Record#parse_record.call_id == Parse_Record#parse_record.call_id ->
                    if
                        Confirm_Timer /= -1 ->
                            timer:cancel(Confirm_Timer);
                        true ->
                            nothing
                    end,
                    {Branch_ID, Cancel_Invite_Request, SIP_Session_State} = uas_core:packet([before_dialog, "487", "Request Cancelled", Old_Parse_Record, self(), State]),
                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, Branch_ID, "487", Cancel_Invite_Request}),

                    remove_b2bua_media_session(B2BUA_Media_PID),
                    New_State = SIP_Session_State#sip_session_state{ua_state = ?REGISTERED, confirm_timer = -1, b2bua_media_listener_pid = -1, parse_record = undefined};
                true ->
                    New_State = State
            end;
        "ACK" ->
            #sip_session_state{transaction_table = Transaction_Table} = State,
            #parse_record{branch_id = Branch_ID} = Parse_Record,
            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            New_State = State;
        "BYE" ->
            #sip_session_state{dialog_table = Dialog_Table, b2bua_media_listener_pid = B2BUA_Media_PID, media_session_pid = Media_Session_PID} = State,
            #parse_record{ip = IP, port = Port, from = From, call_id = Call_ID_Str} = Parse_Record,

            Dialog_List = ets:match_object(Dialog_Table, #dialog{call_id = Call_ID_Str, _='_'}),
            if
                Dialog_List /= [] ->
                    Bye_Request = bye_request(utils:replace_character_to_predefined_xml_entities(From)),
                    gen_server:cast(self(), {response, normal, Bye_Request}),
                    
                    Dialog_State = hd(Dialog_List),
                    {_, Response} = uas_core:packet([after_dialog, "200", "OK", false, Dialog_State, Parse_Record, State]),
                    send_data(State#sip_session_state.socket, udp, IP, Port, Response),
                    remove_b2bua_media_session(B2BUA_Media_PID),
                    remove_media_session(Media_Session_PID),
                    New_State = State#sip_session_state{ua_state = ?REGISTERED, b2bua_media_listener_pid = -1, media_session_pid = -1};
                true ->
                    New_State = State
            end;
        _ ->
            New_State = State
    end,
    {noreply, New_State};

handle_cast({response, Status, Msg}, State) ->
    #sip_session_state{b2bua_session_pid = B2BUA_Session_PID} = State,
    Response = ["<talk_response>", Msg, "</talk_response>"],
    gen_server:cast(B2BUA_Session_PID, {response, Status, Response}),
    {noreply, State};

handle_cast({send_data, Transport_Type, Dest_Server, Dest_Port, Msg}, State) ->
    #sip_session_state{socket = Socket} = State,
    io:format("Inside send_data function ~n"),
    if
        Transport_Type == udp ->
            gen_udp:send(Socket, Dest_Server, Dest_Port, Msg);
        true ->
            nothing
    end,
    {noreply, State};

handle_cast(kill_process, State) ->
    {stop, normal, State};

handle_cast(_Msg, State) ->
	{noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
handle_info({udp, Socket, From_IP, From_Port, Message}, State) ->
    #sip_session_state{ua_state = UA_State, ti_pid = TI_PID} = State,
    if 
        From_Port == ?STUN_PORT ->
            case catch stun:parse_response(Message) of 
                {binding_response, Mapped_Address} ->
                    if 
                        UA_State == ?NOT_REGISTERED ->
                            New_State = State#sip_session_state{mapped_address = Mapped_Address, ua_state = ?REGISTER_IN_PROGRESS},
                            gen_server:cast(self(), {register, ?EXPIRES, New_State});
                        true ->
                            New_State = State#sip_session_state{mapped_address = Mapped_Address}
                    end;
                _ ->
                    if
                        UA_State == ?NOT_REGISTERED ->
                            Return_Response = return_response("register", "critical", "103", "Registration Failed"),
                            gen_server:cast(self(), {response, exit, Return_Response});
                        true ->
                            nothing
                    end,
                    New_State = State
            end;
        true ->
            Parse_Record = parse_sip_packet:parse(Message),
            New_Parse_Record = Parse_Record#parse_record{ip = From_IP, port = From_Port},
            gen_server:cast(TI_PID, {got_message, New_Parse_Record}),
            New_State = State
    end,
    catch inet:setopts(Socket, [{active, once}]),
	{noreply, New_State};

handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
    #sip_session_state{ti_pid = TI_PID, b2bua_media_listener_pid = B2BUA_Media_PID, media_session_pid = Media_Session_PID, socket = Socket, dialog_table = Dialog_Table, 
                       transaction_table = Transaction_Table, deregister_timer = TRef} = State,
    gen_server:cast(TI_PID, kill_process),
    remove_b2bua_media_session(B2BUA_Media_PID),
    remove_media_session(Media_Session_PID),
    if
        TRef /= -1 ->
            timer:cancel(TRef);
        true ->
            nothing
    end,
    catch ets:delete(Dialog_Table),
    catch ets:delete(Transaction_Table),
    io:format("sip_session Terminated ~n"),
    gen_udp:close(Socket).

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
	{ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================

stun_binding_request(Socket, State) ->
    #sip_session_state{stun_timer = Old_TRef} = State,
    if
        Old_TRef /= -1 ->
            catch timer:cancel(Old_TRef);
        true ->
            nothing
    end,
    lists:foldl(fun(X, New_State) ->
                        stun:binding_request(?STUN_SERVER, ?STUN_PORT, Socket),
                        if
                            X == 1 ->
                                case timer:apply_after(?REFRESH_TIMER*1000, gen_server, cast, [self(), {refresh_nat_binding, Socket}]) of
                                    {ok, TRef} ->
                                        New_State#sip_session_state{stun_timer = TRef};
                                    _ ->
                                        New_State#sip_session_state{stun_timer = -1}
                                end;
                            true ->
                                New_State
                        end
                end, State, lists:seq(1, 5)).

create_socket() ->
    case catch gen_udp:open(0, [binary, {active, once}, {reuseaddr, true}]) of
        {ok, Socket} ->
            {ok, Socket};
        _ ->
            error
   end.

return_response(Type, Status, Code, Desc) when is_integer(Code) == true ->
    io_lib:format("<return type='~s' status='~s' code='~w' desc='~s'/>", [Type, Status, Code, Desc]);
return_response(Type, Status, Code, Desc) ->
    io_lib:format("<return type='~s' status='~s' code='~s' desc='~s'/>", [Type, Status, Code, Desc]).

accept_response(Media, Port) ->
    io_lib:format("<accept media='~s' port='~w'/>", [Media, Port]).

call_request(From, Media, Port) ->
    io_lib:format("<call from='~s' media='~s' port='~w'/>", [From, Media, Port]).

cancel_request(From) ->
    io_lib:format("<cancel from='~s'/>", [From]).

bye_request(From) ->
    io_lib:format("<bye from='~s'/>", [From]).

got_provisional_response(R, State) ->
    #parse_record{status = Status} = R,
    Status_Code = Status rem 100,
    case Status_Code of 
        80 ->
            Ring_Response = "<ringing/>",
            gen_server:cast(self(), {response, normal, Ring_Response});
        _ ->
            nothing
    end,
    State.

got_success_response(R, State) ->
    #parse_record{status = Status} = R,
    Status_Code = Status rem 100,
    case Status_Code of 
        0 ->
            #parse_record{branch_id = Branch_ID} = R,
            #sip_session_state{transaction_table = Transaction_Table, mapped_address = Mapped_Address, dialog_table = Dialog_Table} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    Transaction_State = hd(Transaction_List),
                    #transaction{method = Method} = Transaction_State,
                    #parse_record{route = Route, from = From, from_tag = From_Tag, to = To, to_tag = To_Tag, call_id = Call_ID, cseq = CSeq} = R,

                    case Method of 
                        "REGISTER" ->
                            Contact_Record = get_expires(Mapped_Address, R#parse_record.contact),
                            #contact_record{contact = Contact, expires = Expires} = Contact_Record,
                            if
                                Expires == 0 ->
                                    gen_server:cast(self(), kill_process);
                                true ->
                                    timer:apply_after((Expires - 10) * 1000, gen_server, cast, [self(), register])
                            end,
                            Return_Response = return_response("register", "success", "102", "Registration Successful"),
                            gen_server:cast(self(), {response, normal, Return_Response}),

                            Dialog = #dialog{call_id = Call_ID, method = "REGISTER", route = Route, remote_target = Contact, local_cseq = CSeq, remote_cseq = "", local_tag = From_Tag, remote_tag = To_Tag, local_uri = From, remote_uri = To},
                            catch ets:match_delete(Dialog_Table, #dialog{method = "REGISTER", _='_'}),
                            catch ets:insert(Dialog_Table, Dialog),
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            State#sip_session_state{ua_state = ?REGISTERED};
                        "INVITE" ->
                            #parse_record{ip = IP, port = Port, contact = Contact_List, sdp = SDP_Record} = R,
                            #contact_record{contact = Contact} = hd(Contact_List),
                            #sdp{peer_port = Peer_Port, media = Media} = SDP_Record,

                            case catch b2bua_media_listener:start() of 
                                {ok, B2BUA_Media_PID} ->
                                    case catch gen_server:call(B2BUA_Media_PID, create_socket, infinity) of 
                                        {ok, B2BUA_Media_Port} ->
                                            To_Send_Bye = false,
                                        
                                            [_, T1] = re:split(Contact, "@", [{return, list}]),
                                            Peer_IP = re:replace(T1, "\\>", "", [{return, list}]),
                                            
                                            gen_server:cast(State#sip_session_state.media_session_pid, {set_peer_address_and_media, {Peer_IP, Peer_Port}, Media}),
                                            
                                            Dialog = #dialog{call_id = Call_ID, method = "INVITE", route = Route, remote_target = Contact, local_cseq = CSeq, remote_cseq = "", local_tag = From_Tag, remote_tag = To_Tag, local_uri = From, remote_uri = To},
                                            catch ets:match_delete(Dialog_Table, #dialog{method = "INVITE", _='_'}),
                                            catch ets:insert(Dialog_Table, Dialog),
                                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                                        
                                            Accept_Response = accept_response(Media, B2BUA_Media_Port),
                                            gen_server:cast(self(), {response, normal, Accept_Response}),
                                        
                                            Request = uac_core:packet([ack, R, State]),
                                            send_data(State#sip_session_state.socket, udp, IP, Port, Request),
                                            Modified_State = State#sip_session_state{b2bua_media_listener_pid = B2BUA_Media_PID};
                                        _ ->
                                            To_Send_Bye = true,
                                            remove_b2bua_media_session(B2BUA_Media_PID),
                                            remove_media_session(State#sip_session_state.media_session_pid),
                                            Modified_State = State
                                    end;
                                _ ->
                                    To_Send_Bye = true,
                                    remove_media_session(State#sip_session_state.media_session_pid),
                                    Modified_State = State
                            end,
                            if
                                To_Send_Bye == true ->
                                    ACK_Request = uac_core:packet([ack, R, Modified_State]),
                                    send_data(State#sip_session_state.socket, udp, IP, Port, ACK_Request),
                                    
                                    New_Dialog = #dialog{call_id = Call_ID, method = "INVITE", route = Route, remote_target = Contact, local_cseq = CSeq, remote_cseq = "", local_tag = From_Tag, remote_tag = To_Tag, local_uri = From, remote_uri = To},
                                    catch ets:match_delete(Dialog_Table, #dialog{method = "INVITE", _='_'}),
                                    catch ets:insert(Dialog_Table, New_Dialog),
                                    catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                                    
                                    {New_Branch_ID, Bye_Request, New_State} = uac_core:packet([bye, New_Dialog, Modified_State]),
                                    gen_server:cast(Modified_State#sip_session_state.ti_pid, {create_ti, {udp, IP, Port}, New_Branch_ID, "BYE", Bye_Request}),
                                    New_State#sip_session_state{media_session_pid = -1};
                                true ->
                                    Modified_State
                            end;
                        "BYE" ->
                            #sip_session_state{b2bua_media_listener_pid = B2BUA_Media_PID, media_session_pid = Media_Session_PID} = State,
                            remove_b2bua_media_session(B2BUA_Media_PID),
                            remove_media_session(Media_Session_PID),
                            catch ets:match_delete(Dialog_Table, #dialog{method = "INVITE", call_id = Call_ID, _='_'}),
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            State#sip_session_state{ua_state = ?REGISTERED};
                        _ ->
                            State
                    end;
                true ->
                    #parse_record{route = Route, from = From, from_tag = From_Tag, to = To, to_tag = To_Tag, call_id = Call_ID, cseq = CSeq} = R,
                    case R#parse_record.method of 
                        "REGISTER" ->
                            Contact_Record = get_expires(Mapped_Address, R#parse_record.contact),
                            #contact_record{contact = Contact, expires = Expires} = Contact_Record,
                            if
                                Expires == 0 ->
                                    gen_server:cast(self(), kill_process);
                                true ->
                                    timer:apply_after((Expires - 10) * 1000, gen_server, cast, [self(), register])
                            end,
                            timer:apply_after(Expires * 1000, gen_server, cast, [self(), register]),
                            Return_Response = return_response("register", "success", "102", "Registration Successful"),
                            gen_server:cast(self(), {response, normal, Return_Response}),

                            Dialog = #dialog{call_id = Call_ID, method = "REGISTER", route = Route, remote_target = Contact, local_cseq = CSeq, remote_cseq = "", local_tag = From_Tag, remote_tag = To_Tag, local_uri = From, remote_uri = To},
                            catch ets:match_delete(Dialog_Table, #dialog{method = "REGISTER", _='_'}),
                            catch ets:insert(Dialog_Table, Dialog),
                            State#sip_session_state{ua_state = ?REGISTERED};
                        _ ->
                            State
                    end
            end;
        _ ->
            State
    end.

got_redirect_response(_R, State) ->
    State.

got_client_error_response(R, State) ->
    #sip_session_state{ti_pid = TI_PID} = State,
    #parse_record{status = Status} = R,
    Status_Code = Status rem 100,
    case Status_Code of 
        1 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, realm = Realm, nonce = Nonce, qop = QOP} = R,
            #sip_session_state{userid = Userid, password = Password, transaction_table = Transaction_Table} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    Transaction_State = hd(Transaction_List),
                    #transaction{transaction_record = Transaction_Record} = Transaction_State,
                    #transaction_record{expires = Expires_Time, auth_count = Auth_Count, nonce = Old_Nonce, nonce_count = Old_Nonce_Count} = Transaction_Record,
                    if
                        Auth_Count < 3 orelse R#parse_record.stale == "true" ->
                            if
                                Old_Nonce == Nonce ->
                                    Nonce_Count = Old_Nonce_Count + 1,
                                    {Client_Nonce, Digest} = auth_digest([Userid, Password, Realm, Nonce, QOP, "sip:" ++ ?SIP_SERVER, "REGISTER", Nonce_Count]);
                                true ->
                                    Nonce_Count = 1,
                                    {Client_Nonce, Digest} = auth_digest([Userid, Password, Realm, Nonce, QOP, "sip:" ++ ?SIP_SERVER, "REGISTER", 1])
                            end,
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            {New_Branch_ID, Request, New_State} = uac_core:packet([register, auth, Expires_Time, Digest, Auth_Count + 1, Realm, Nonce, Nonce_Count, Client_Nonce, QOP, State]),
                            gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, New_Branch_ID, "REGISTER", Request}),
                            New_State;
                        true ->
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            New_State = State,
                            Return_Response = return_response("register", "critical", "103", "Registration Failed"),
                            gen_server:cast(self(), {response, exit, Return_Response})
                    end;
                true ->
                    New_State = State,
                    Return_Response = return_response("register", "critical", "103", "Registration Failed"),
                    gen_server:cast(self(), {response, exit, Return_Response})
            end,
            New_State;
        3 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, method = Method} = R,
            #sip_session_state{socket = Socket, media_session_pid = Media_Session_PID, transaction_table = Transaction_Table} = State,
            case Method of 
                "INVITE" ->
                    remove_media_session(Media_Session_PID),
                    Return_Response = return_response("invite", "error", "106", "Call Failed"),
                    gen_server:cast(self(), {response, error, Return_Response}),
                    Request = uac_core:packet([ack, R, State]),
                    send_data(Socket, udp, IP, Port, Request),
                    New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                _ ->
                    New_State = State
            end,
            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            New_State;
        7 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, realm = Realm, nonce = Nonce, qop = QOP} = R,
            #sip_session_state{userid = Userid, password = Password, transaction_table = Transaction_Table, media_session_pid = Media_Session_PID} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    Transaction_State = hd(Transaction_List),
                    #transaction{transaction_record = Transaction_Record} = Transaction_State,
                    #transaction_record{auth_count = Auth_Count, nonce = Old_Nonce, nonce_count = Old_Nonce_Count, to = To_ID} = Transaction_Record,
                    if
                        Auth_Count < 3 orelse R#parse_record.stale == "true" ->
                            if
                                Old_Nonce == Nonce ->
                                    Nonce_Count = Old_Nonce_Count + 1,
                                    {Client_Nonce, Digest} = auth_digest([Userid, Password, Realm, Nonce, QOP, "sip:" ++ ?SIP_SERVER, "INVITE", Nonce_Count]);
                                true ->
                                    Nonce_Count = 1,
                                    {Client_Nonce, Digest} = auth_digest([Userid, Password, Realm, Nonce, QOP, "sip:" ++ ?SIP_SERVER, "INVITE", 1])
                            end,    
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            case catch uac_core:packet([invite, auth, To_ID, Digest, Auth_Count + 1, Realm, Nonce, Nonce_Count, Client_Nonce, QOP, self(), State]) of
                                {error, Code} ->
                                    remove_media_session(Media_Session_PID),
                                    Return_Response = return_response("invite", "error", Code, "Call Failed"),
                                    gen_server:cast(self(), {response, normal, Return_Response}),
                                    New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                                {New_Branch_ID, Request, New_State} -> 
                                    gen_server:cast(TI_PID, {create_ti, {udp, IP, Port}, New_Branch_ID, "INVITE", Request})
                            end;
                        true ->
                            remove_media_session(Media_Session_PID),
                            catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                            New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1},
                            Return_Response = return_response("invite", "error", "106", "Call Failed"),
                            gen_server:cast(self(), {response, normal, Return_Response})
                    end;
                true ->
                    remove_media_session(Media_Session_PID),
                    New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1},
                    Return_Response = return_response("invite", "error", "106", "Call Failed"),
                    gen_server:cast(self(), {response, normal, Return_Response})
            end,
            New_State;
        87 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, method = Method} = R,
            #sip_session_state{socket = Socket, transaction_table = Transaction_Table, media_session_pid = Media_Session_PID} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    case Method of
                        "INVITE" ->
                            remove_media_session(Media_Session_PID),
                            Request = uac_core:packet([ack, R, State]),
                            send_data(Socket, udp, IP, Port, Request),
                            New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                        _ ->
                            New_State = State
                    end,
                    catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                    New_State;
                true ->
                    case Method of
                        "INVITE" ->
                            remove_media_session(Media_Session_PID),
                            New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                        _ ->
                            New_State = State
                    end,
                    New_State
            end;
        88 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, method = Method, call_id = Call_ID_Str} = R,
            #sip_session_state{socket = Socket, transaction_table = Transaction_Table} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    case Method of
                        "INVITE" ->
                            #sip_session_state{dialog_table = Dialog_Table, media_session_pid = Media_Session_PID} = State,
                            Dialog_List = ets:match_object(Dialog_Table, #dialog{call_id = Call_ID_Str, _='_'}),
                            if
                                length(Dialog_List) >= 1 ->
                                    New_State = State;
                                true ->
                                    remove_media_session(Media_Session_PID),
                                    Request = uac_core:packet([ack, R, State]),
                                    send_data(Socket, udp, IP, Port, Request),

                                    Return_Response = return_response("invite", "error", "106", "Call Failed"),
                                    gen_server:cast(self(), {response, normal, Return_Response}),
                                    
                                    New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1}
                            end;
                        _ ->
                            New_State = State
                    end,
                    catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                    New_State;
                true ->
                    case Method of
                        "INVITE" ->
                            #sip_session_state{dialog_table = Dialog_Table, media_session_pid = Media_Session_PID} = State,
                            Dialog_List = ets:match_object(Dialog_Table, #dialog{call_id = Call_ID_Str, _='_'}),
                            if
                                length(Dialog_List) >= 1 ->
                                    New_State = State;
                                true ->
                                    remove_media_session(Media_Session_PID),
                                    New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1}
                            end;
                        _ ->
                            New_State = State
                    end,
                    New_State
            end;
        _ ->
            State
    end.

got_server_error_response(_R, State) ->
    State.

got_global_error_response(R, State) ->
    #parse_record{status = Status} = R,
    Status_Code = Status rem 100,
    case Status_Code of 
        3 ->
            #parse_record{ip = IP, port = Port, branch_id = Branch_ID, method = Method} = R,
            #sip_session_state{socket = Socket, transaction_table = Transaction_Table, media_session_pid = Media_Session_PID} = State,
            Transaction_List = ets:match_object(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
            if
                length(Transaction_List) >= 1 ->
                    case Method of 
                        "INVITE" ->
                            remove_media_session(Media_Session_PID),
                            Return_Response = return_response("invite", "error", "107", "User Busy"),
                            gen_server:cast(self(), {response, error, Return_Response}),
                            Request = uac_core:packet([ack, R, State]),
                            send_data(Socket, udp, IP, Port, Request),
                            New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                        _ ->
                            New_State = State
                    end,
                    catch ets:match_delete(Transaction_Table, #transaction{branch_id = Branch_ID, _='_'}),
                    New_State;
                true ->
                    case Method of 
                        "INVITE" ->
                            remove_media_session(Media_Session_PID),
                            New_State = State#sip_session_state{ua_state = ?REGISTERED, media_session_pid = -1};
                        _ ->
                            New_State = State
                    end,
                    New_State
            end;
        _ ->
            State
    end.

auth_digest([Userid, Password, Realm, Nonce, QOP, Digest_URI, Method, Int_Nonce_Count]) ->
    HA1 = hex:to(crypto:md5(Userid ++ ":" ++ Realm ++ ":" ++ Password)),
    if
        QOP == "auth" orelse QOP == undefined ->
            HA2 = hex:to(crypto:md5(Method ++ ":" ++ Digest_URI));
        true ->
            HA2 = hex:to(crypto:md5(Method ++ ":" ++ Digest_URI))
    end,
    if
        QOP == "auth" orelse QOP == "auth-int" ->
            Client_Nonce = utils:generate_random_string(),
            Nonce_Count = "0000000" ++ integer_to_list(Int_Nonce_Count),
            {Client_Nonce, hex:to(crypto:md5(HA1 ++ ":" ++ Nonce ++ ":" ++ Nonce_Count ++ ":" ++ Client_Nonce ++ ":" ++ QOP ++ ":" ++ HA2))};
        true ->
            {undefined, hex:to(crypto:md5(HA1 ++ ":" ++ Nonce ++ ":" ++ HA2))}
    end.

get_expires(Mapped_Address, Contact) ->
    Port = integer_to_list(element(2, Mapped_Address)),
    Contact_List = 
        [ X || X <- Contact, 
               case re:run(X#contact_record.contact, Port, []) of
                   {match, _} -> true;
                   _ -> false
               end ],
    if 
        length(Contact_List) >= 1 ->
            hd(Contact_List);
        true ->
            #contact_record{expires = 0, contact = ""}
    end.

send_data(Socket, Transport_Type, Dest_Server, Dest_Port, Msg) ->
    io:format("Inside send_data function ~n"),
    if
        Transport_Type == udp ->
            gen_udp:send(Socket, Dest_Server, Dest_Port, Msg);
        true ->
            nothing
    end.

remove_b2bua_media_session(B2BUA_Media_PID) ->
    if
        B2BUA_Media_PID /= -1 ->
            gen_server:cast(B2BUA_Media_PID, kill_process);
        true ->
            nothing
    end.

remove_media_session(Media_Session_PID) ->
    if
        Media_Session_PID /= -1 ->
            gen_server:cast(Media_Session_PID, kill_process);
        true ->
            nothing
    end.
